﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Styx.Combat.CombatRoutine;
using Styx.Helpers;
using TreeSharp;
using Styx.Logic.Combat;
using Styx.WoWInternals.WoWObjects;
using Styx;
using Styx.WoWInternals;
using Styx.Logic.Pathing;
using System.Threading;
using Styx.Patchables;
using Styx.Logic.BehaviorTree;
using System.Drawing;
using FelMaster.ClassButton;

namespace FelMaster
{
    public class SimCraftBase
    {
        private LocalPlayer Me { get { return StyxWoW.Me; } }

        struct SpellRadiusEntry
        {
            public int id;
            public float radius;
            public float perLevel;
            public float max;
        }
        WoWDb.DbTable spellRadiusTable = StyxWoW.Db[ClientDb.SpellRadius];

        private static SimCraftBase instance = new SimCraftBase();
        public static SimCraftBase Instance
        {
            get
            {
                return instance;
            }
        }

        private SimCraftBase()
        {
            BotEvents.OnBotStarted += OnStarted;
            BotEvents.OnBotStopped += OnStopped;

            WoWDb.DbTable spellTable = StyxWoW.Db[ClientDb.Spell];
        }

        private void OnStarted(object o)
        {
            Log("Events attached.");
            
            //means spell was cast (did not hit target yet)
            Lua.Events.AttachEvent("UNIT_SPELLCAST_SUCCEEDED", OnSpellFired_ACK);

            //user got stunned, silenced, kicked...
            Lua.Events.AttachEvent("UNIT_SPELLCAST_INTERRUPTED", OnSpellFired_NACK);

            //misc fails, due to stopcast, spell spam, etc.
            Lua.Events.AttachEvent("UNIT_SPELLCAST_FAILED", OnSpellFired_FAIL);
            Lua.Events.AttachEvent("UNIT_SPELLCAST_FAILED_QUIET", OnSpellFired_FAIL);
            Lua.Events.AttachEvent("UNIT_SPELLCAST_STOP", OnSpellFired_FAIL);

            //enter/leave combat
            Lua.Events.AttachEvent("PLAYER_REGEN_ENABLED", OnRegenEnabled);
            Lua.Events.AttachEvent("PLAYER_REGEN_DISABLED", OnRegenDisabled);

            burstLevel = 0;
            combatStart = DateTime.MinValue;
        }
        private void OnStopped(object o)
        {
            Lua.Events.DetachEvent("UNIT_SPELLCAST_SUCCEEDED", OnSpellFired_ACK);

            Lua.Events.DetachEvent("UNIT_SPELLCAST_INTERRUPTED", OnSpellFired_NACK);

            Lua.Events.DetachEvent("UNIT_SPELLCAST_FAILED", OnSpellFired_FAIL);
            Lua.Events.DetachEvent("UNIT_SPELLCAST_FAILED_QUIET", OnSpellFired_FAIL);
            Lua.Events.DetachEvent("UNIT_SPELLCAST_STOP", OnSpellFired_FAIL);

            Lua.Events.DetachEvent("PLAYER_REGEN_ENABLED", OnRegenEnabled);
            Lua.Events.DetachEvent("PLAYER_REGEN_DISABLED", OnRegenDisabled);
        }

        private uint burstLevel = 0;
        public uint BurstLevel
        {
            get
            {
                if (PlayerIsManagingCooldowns)
                    return burstLevel;
                else
                {
                    //dont overaggro everything on pull!
                    if (CombatTimeElapsed.TotalSeconds > 10)
                    {
                        return 999;
                    }
                    else
                    {
                        return 0;
                    }
                }
            }
        }

        public void SetBurstLevel(uint burst)
        {
            burstLevel = burst;
        }

        public bool PlayerIsManagingCooldowns { get { return !SettingsFile.Instance.CurrentMode_HandleCooldowns; } }
        public bool AutoManageCooldowns { get { return SettingsFile.Instance.CurrentMode_HandleCooldowns; } }

        private DateTime combatStart = DateTime.MinValue;
        public DateTime CombatStart { get { return combatStart; } }
        public TimeSpan CombatTimeElapsed { get { return combatStart == DateTime.MinValue ? new TimeSpan(0) : DateTime.Now.Subtract(combatStart); } }

        public float GetSpellRadius(string name)
        {
            try
            {
                var s = SpellManager.Spells[name];
                var radiusIndex = s.SpellEffect1.RadiusIndex;
                var entry = spellRadiusTable.GetRow(radiusIndex).GetStruct<SpellRadiusEntry>();
                return entry.radius;
            }
            catch
            {
                return 0;
            }
        }

        private string[] COMBAT_SPELL_MISS_TYPE = new string[] {
            "BLOCK",
            "DEFLECT",
            "DODGE",
            "EVADE",
            "IMMUNE",
            "MISS",
            "PARRY",
            "REFLECT",
            "RESIST"
        };
        private void OnCombatLogEvent(object sender, LuaEventArgs raw)
        {
            var args = raw.Args;
            var eventType = Convert.ToString(args[1]);
            var sourceGuid = Convert.ToUInt64(args[3]);

            if (sourceGuid != Me.Guid || (Me.Pet != null && sourceGuid != Me.Pet.Guid)) {
                return;
            }

            if (eventType == "SPELL_MISSED") {
                var missType = Convert.ToString(args[14]);
                if (COMBAT_SPELL_MISS_TYPE.Contains(missType)) {
                    var spellId = Convert.ToInt32(args[11]);
                    var spellName = WoWSpell.FromId(spellId).Name;
                    
                    if (_locks.ContainsKey(spellName)) {
                        //failed to be cast => spam it!
                        _locks[spellName] = DateTime.Now;
                        Log("Spell missed: {0}. Reset spell lock", spellName);
                    }
                    else {
                        Log("Spell missed: {0} (no lock, don't care)", spellName);
                    }
                }
            }
        }

        private void OnSpellFired_ACK(object sender, LuaEventArgs raw)
        {
            OnSpellFired(true, true, raw);
        }
        private void OnSpellFired_NACK(object sender, LuaEventArgs raw)
        {
            OnSpellFired(false, true, raw);
        }
        private void OnSpellFired_FAIL(object sender, LuaEventArgs raw)
        {
            OnSpellFired(false, false, raw);
        }

        private void OnSpellFired (bool success, bool spellCast, LuaEventArgs raw)
        {
            var args = raw.Args;
            var player = Convert.ToString(args[0]);

            if (player != "player")
            {
                return;
            }

            //get the english spell name, not the localized one!
            var spellID = Convert.ToInt32(args[4]);
            string spellName = WoWSpell.FromId(spellID).Name;

            /*
            if (!success && spellCast)
            {
                Log("Woops, '{0}' cast failed: {1}", spellName, raw.EventName);
            }
             */

            //if that spell got a lock, let's extend it (spell travel time + client lag) / or reset it...
            if (_locks.ContainsKey(spellName))
            {
                if (success)
                {
                    //yay!
                    _locks[spellName] = DateTime.Now.AddSeconds(CLIENT_LAG + 4.0);
                }
                else
                {
                    if (spellCast)
                    {
                        //interrupted while casting
                        _locks[spellName] = DateTime.Now;
                    }
                    else
                    {
                        //failed to cast it. moar spam!
                        _locks[spellName] = DateTime.Now;
                    }
                }
            }
        }

        public Composite BurstSequence(params Composite[] children)
        {
            var ch = children.ToList();
            ch.Add(new TreeSharp.Action(a => RunStatus.Success));
            return new DecoratorContinue(x => SimCraftBase.Instance.BurstLevel > 0, new PrioritySelector(ch.ToArray()));
        }

        private void OnRegenEnabled(object sender, LuaEventArgs raw)
        {
            //player left combat
            combatStart = DateTime.MinValue;
        }
        private void OnRegenDisabled(object sender, LuaEventArgs raw)
        {
            //player entered combat
            burstLevel = 0;
            combatStart = DateTime.Now;
            Log("Entered combat mode. Reset burst level to 0");
        }

        private const double CLIENT_LAG = 1.0; //s
        
        public void Log(string s, params object[] args)
        {
            if (s != null)
                Logging.Write(Color.PeachPuff, "[SimCraftBase] " + s, args);
        }

        private Dictionary<string, double> _uniqueLocks = new Dictionary<string, double>();
        public Composite CastUniqueSpell(string name, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    return cond(a) && CanCast(name);
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => CastSpell(name)),
                    new TreeSharp.Action(a => _uniqueLocks[name] = CastTime(name)*1.5+10)
                )
            );
        }

        public Composite CastAreaSpell(string name, double radius, bool requiresTerrainClick, int minAffectedTargets, double minRange,
            double MaxRange, CanRunDecoratorDelegate cond, string label)
        {
            var bestLocation = WoWPoint.Empty;
            return new Decorator(
                delegate(object a)
                {
                    if (!cond(a) || !CanCast(name))
                        return false;
                    bestLocation = GroupBase.Instance.FindPackedTargets(radius, minRange, MaxRange, minAffectedTargets, GroupBase.Instance.Logic == GroupBase.GroupLogic.BATTLEGROUND);
                    return bestLocation != WoWPoint.Empty;
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => CastSpell(name)),
                    new DecoratorContinue(x => requiresTerrainClick,
                        new TreeSharp.Action(a => LegacySpellManager.ClickRemoteLocation(bestLocation))
                    )
                )
            );
        }

        public Composite UseBagItem(string name, CanRunDecoratorDelegate cond, string label)
        {
            WoWItem item = null;
            return new Decorator(
                delegate(object a)
                {
                    if (!cond(a))
                        return false;
                    item = Me.BagItems.FirstOrDefault(x => x.Name == name && x.Usable && x.Cooldown <= 0);
                    return item != null;
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => item.UseContainerItem())
                )
            );
        }

        public Composite CastSpellAtLocation(string name, WoWPoint location, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    return cond(a) && CanCast(name);
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => CastSpell(name)),
                    new TreeSharp.Action(a => LegacySpellManager.ClickRemoteLocation(location))
                )
            );
        }

        public Composite CastSpellAtTargetLocation(string name, CanRunDecoratorDelegate cond, string label)
        {
            return CastSpellAtLocation(name, Me.CurrentTarget.Location, cond, label);
        }

        public Composite ChannelAreaSpell(string name, double radius, bool requiresTerrainClick, int minAffectedTargets, double minRange,
            double MaxRange, CanRunDecoratorDelegate cond, string label)
        {
            var bestLocation = WoWPoint.Empty;
            return new Decorator(
                delegate(object a)
                {
                    if (!cond(a) || !CanCast(name))
                        return false;
                    bestLocation = GroupBase.Instance.FindPackedTargets(radius, minRange, MaxRange, minAffectedTargets, GroupBase.Instance.Logic == GroupBase.GroupLogic.BATTLEGROUND);
                    return bestLocation != WoWPoint.Empty;
                },
                new PrioritySelector(
                    //dont break it if already casting it
                    new Decorator(x => PlayerIsChanneling && Me.ChanneledCastingSpellId == SpellManager.Spells[name].Id,
                        new TreeSharp.Action(a => Log("Player is already channeling AOE {0} - don't recast", name))
                    ),
                    //casting logic
                    new Sequence(
                        new TreeSharp.Action(a => Log(label)),
                        new TreeSharp.Action(a => CastSpell(name)),
                        new DecoratorContinue(x => requiresTerrainClick,
                            new TreeSharp.Action(a => LegacySpellManager.ClickRemoteLocation(bestLocation))
                        )
                    )
                )
            );
        }

        public Composite RunMacroText(string macro, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(cond,
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => Lua.DoString("RunMacroText(\"" + RealLuaEscape(macro) + "\")"))
                )
            );
        }

        public Composite CastSpell(string name, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    return cond(a) && CanCast(name);
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => CastSpell(name))
                )
            );
        }

        private HashSet<string> knownChanneledSpells = new HashSet<string>();
        public Composite ChannelSpell(string name, CanRunDecoratorDelegate cond, string label)
        {
            knownChanneledSpells.Add(name);
            return new PrioritySelector(
                new Decorator(x => PlayerIsChanneling && Me.ChanneledCastingSpellId == SpellManager.Spells[name].Id, 
                    new TreeSharp.Action(a => Log("Player is already channeling {0} - don't recast", name))
                ),
                CastSpell(name, cond, label)
            );
        }

        public Composite CastConicSpell(string name, float maxDistance, float maxAngleDelta_Degrees, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    return cond(a) && CanCast(name) &&
                        DistanceToTargetBoundingBox() <= maxDistance &&
                        FacingTowardsUnit_Degrees(Me.Location, Me.CurrentTarget.Location) <= maxAngleDelta_Degrees;
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => CastSpell(name))
                )
            );
        }

        private Dictionary<string, DateTime> _locks = new Dictionary<string, DateTime>();
        public Dictionary<string, double> DumpSpellLocks()
        {
            var ret = new Dictionary<string, double>();
            var now = DateTime.Now;

            foreach (var x in _locks)
            {
                var s = x.Value.Subtract(now).TotalSeconds;
                if (s < 0) s = 0;
                s = Math.Round(s, 3);
                ret[x.Key] = s;
            }

            return ret;
        }

        public Composite CastDebuff(string name, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    var X = true;
                    try
                    {
                        X = DateTime.Now.Subtract(_locks[name]).TotalSeconds > 0;
                    }
                    catch { }

                    return X &&
                        cond(a) && CanCast(name) && TargetDebuffTimeLeft(name) < DotDelta(name);
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => CastSpell(name)),
                    new TreeSharp.Action(a => _locks[name] = DateTime.Now.AddSeconds(CastTime(name)*1.5+CLIENT_LAG))
                )
            );
        }

        public Composite CastOffensiveBuff(string spell, string buff, double maxTimeLeft, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    var X = true;
                    try
                    {
                        X = DateTime.Now.Subtract(_locks[spell]).TotalSeconds > 0;
                    }
                    catch { }

                    return X && CanCast(spell) && PlayerBuffTimeLeft(buff) < maxTimeLeft;
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label == null ? null : (label + " (time left = " + PlayerBuffTimeLeft(buff) + ")"))),
                    new TreeSharp.Action(a => CastSpell(spell)),
                    new TreeSharp.Action(a => _locks[spell] = DateTime.Now.AddSeconds(CastTime(spell)*1.5 + CLIENT_LAG))
                )
            );
        }

        public bool PlayerHasBuff(string name)
        {
            name = LocalizeSpellName(name);
            try
            {
                var lua = string.Format("local x=UnitBuff('player', \"{0}\"); if x==nil then return 0 else return 1 end", RealLuaEscape(name));
                return Lua.GetReturnValues(lua)[0] == "1";
            }
            catch
            {
                Log("Lua failed in PlayerHasBuff");
                return false;
            }
        }

        private string[] control_debuffs = new string[]{
            "Bind Elemental",
            "Hex",
            "Polymorph",
            "Hibernate",
            "Entangling Roots",
            "Freezing Trap",
            "Wyvern Sting",
            "Repentance",
            "Psychic Scream",
            "Sap",
            "Blind",
            "Fear",
            "Seduction",
            "Howl of Terror"
        };
        private string[] control_unbreakable_debuffs = new string[]{
            "Cyclone",
            "Mind Control",
            "Banish"
        };
        public bool UnitIsControlled(WoWUnit unit, bool breakOnDamageOnly)
        {
            return unit.ActiveAuras.Any(x => x.Value.IsHarmful && 
                (control_debuffs.Contains(x.Value.Name) || (!breakOnDamageOnly && control_unbreakable_debuffs.Contains(x.Value.Name)))
            );
        }

        public bool TargetHasOneOfTheseDebuffs(bool appliedByMe, params string[] debuffs)
        {
            for (var i = 0; i < debuffs.Length; i++)
                debuffs[i] = LocalizeSpellName(debuffs[i]);

            var lua = "";
            try
            {
                foreach (var x in debuffs)
                {
                    if (appliedByMe)
                        lua += string.Format("if UnitDebuff('target', \"{0}\", nil, 'PLAYER') ~= nil then return 1 end;", RealLuaEscape(x));
                    else
                        lua += string.Format("if UnitDebuff('target', \"{0}\") ~= nil then return 1 end;", RealLuaEscape(x));
                }
                lua += "return 0";
                return Lua.GetReturnValues(lua)[0] == "1";
            }
            catch
            {
                Log("Lua failed in TargetHasOneOfTheseDebuffs: "+lua);
                return false;
            }
        }

        public bool TargetHasDebuff(string name)
        {
            return TargetHasOneOfTheseDebuffs(true, new string[1] { name });
        }

        public double DotDelta(string name)
        {
            return 2.0;
        }

        public double CastTime(string name)
        {
            try
            {
                var s = SpellManager.Spells[name];
                return (double)s.CastTime / 1000.0;
            }
            catch
            {
                return 999999.9;
            }
        }

        public bool PlayerIsChanneling
        {
            get
            {
                var lua = string.Format("if UnitChannelInfo('player') == nil then return 0 else return 1 end");
                try
                {
                    return Lua.GetReturnValues(lua)[0] == "1";
                }
                catch
                {
                    Log("Lua failed in PlayerIsChanneling: " + lua);
                    return true;
                }
            }
        }

        public int[] Trinkets
        {
            get
            {
                var raw = Lua.GetReturnValues("return GetInventoryItemID('player',13)..'|'..GetInventoryItemID('player',14)")[0];
                var a = raw.Split('|');
                return new int[2]{
                    int.Parse(a[0]),
                    int.Parse(a[1])
                };
            }
        }

        public Composite UseEquippedItem(int slotId, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(delegate(object a)
            {
                return cond(a) && IsEquippedItemUsable(slotId); //true if item can have a cooldown and is not on cooldown
            },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => Lua.DoString("UseInventoryItem("+slotId+")"))
                )
            );
        }

        public Composite UseTrinket(int[] trinketIDs, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    var T = Trinkets;
                    return cond(a) &&
                        (
                            (trinketIDs.Contains(T[0]) && IsEquippedItemUsable(13)) ||
                            (trinketIDs.Contains(T[1]) && IsEquippedItemUsable(14))
                        );
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(delegate
                    {
                        var T = Trinkets;
                        var slot = 13;
                        var t0 = trinketIDs.Contains(T[0]) && IsEquippedItemUsable(13);
                        if (!t0)
                            slot = 14;
                        Lua.DoString("UseInventoryItem("+slot+")");
                    })
                )
            );
        }
        public Composite UseTrinket(int trinketID, CanRunDecoratorDelegate cond, string label)
        {
            return UseTrinket(new int[1] { trinketID }, cond, label);
        }

        public Composite UseEngineerGloves(CanRunDecoratorDelegate cond, string label)
        {
            return UseEquippedItem(10, cond, label);
        }

        public int SpellCost(string name)
        {
            try
            {
                var lua = "local name, rank, icon, cost, isFunnel, powerType, castTime, minRange, maxRange = GetSpellInfo({0}); return cost";
                var cost = int.Parse(Lua.GetReturnValues(string.Format(lua, SpellManager.Spells[name].Id))[0]);
                return cost;
            }
            catch
            {
                Log("Lua failed in SpellCost");
                return 99999999;
            }
        }

        public bool IsEquippedItemUsable(int slotId)
        {
            try
            {
                var raw = Lua.GetReturnValues("local start, duration, enable = GetInventoryItemCooldown('player', " + slotId + "); if start == 0 and enable == 1 then return 1 else return 0 end");
                return raw[0] == "1";
            }
            catch
            {
                Log("Lua failed in IsItemUsable");
                return false;
            }
        }

        public bool PlayerIsCasting
        {
            get
            {
                var lua = string.Format("if UnitCastingInfo('player') == nil then return 0 else return 1 end");
                try
                {
                    return Lua.GetReturnValues(lua)[0] == "1";
                }
                catch
                {
                    Log("Lua failed in PlayerIsCasting: " + lua);
                    return true;
                }
            }
        }

        public static string RealLuaEscape(string luastring)
        {
            var bytes = UTF8Encoding.UTF8.GetBytes(luastring);
            var ret = "";
            foreach (var b in bytes)
            {
                ret += "\\" + b;
            }
            return ret;
        }

        private Dictionary<string, string> _localizedSpellNames = new Dictionary<string, string>();
        public string LocalizeSpellName(string name)
        {
            if (_localizedSpellNames.ContainsKey(name))
                return _localizedSpellNames[name];

            string loc;

            int id = 0;
            try
            {
                id = SpellManager.Spells[name].Id;
            }
            catch
            {
                //then it might be a buff name instead of a spell name...
                try
                {
                    loc = AuraTranslator.Instance.LocalizeAuraName(name);
                    _localizedSpellNames[name] = loc;
                    Log("Localized aura: '" + name + "' is '" + loc + "'.");
                    return name;
                }
                catch
                {
                    //last try: use rawspells.
                    try
                    {
                        id = SpellManager.RawSpells.First(x => x.Name == name).Id;
                    }
                    catch
                    {
                        //Logging.Write(Color.Red, "Failed to localize spell name: " + name);
                        return name;
                    }
                }
            }

            loc = Lua.GetReturnValues("return select(1, GetSpellInfo(" + id + "))")[0];
            _localizedSpellNames[name] = loc;
            Log("Localized spell: '" + name + "' is '" + loc + "'.");
            return loc;
        }

        public float DistanceToTargetBoundingBox()
        {
            return Me.CurrentTarget == null ? 999999f : DistanceToTargetBoundingBox(Me.CurrentTarget);
        }

        public float DistanceToTargetBoundingBox(WoWUnit target)
        {
            return (float) Math.Max(0f, target.Distance - target.BoundingRadius);
        }

        public float FacingTowardsUnit_Radians(WoWPoint me, WoWPoint target)
        {
            try
            {
                var direction = me.GetDirectionTo(target);
                direction.Normalize();
                var myFacing = ObjectManager.Me.Rotation;

                //real and safe tan reverse function
                double ret = Math.Atan2(direction.Y, direction.X);

                var alpha = Math.Abs(myFacing - ret);
                if (alpha > Math.PI) alpha = Math.Abs(2 * Math.PI - alpha);

                if (double.IsNaN(alpha))
                    return 0f;
                else
                    return (float)alpha;
            }
            catch
            {
                return 0f;
            }
        }

        public float FacingTowardsUnit_Degrees(WoWPoint me, WoWPoint target)
        {
            return (float) (FacingTowardsUnit_Radians(me, target) * 180.0 / Math.PI);
        }

        public bool IsChanneledSpell(string name) { return knownChanneledSpells.Contains(name); }

        public bool CanCast(string name)
        {
            return (!Me.IsMoving || (CastTime(name) == 0 && !IsChanneledSpell(name))) && IsUsableSpell(name) && SpellCooldown(name) <= 0.5;
        }

        public void CastSpell(string name)
        {
            name = LocalizeSpellName(name);
            //Logging.WriteDebug("CastSpellByName(\"{0}\")", RealLuaEscape(name)); // <- this aint really readable now that I'm escaping everything like crazy...
            Lua.DoString(string.Format("CastSpellByName(\"{0}\")", RealLuaEscape(name)));
        }

        public double SpellCooldown(string name)
        {
            name = LocalizeSpellName(name);
            var lua = string.Format("local x,y=GetSpellCooldown(\"{0}\"); return x+y-GetTime()", RealLuaEscape(name));
            try
            {
                return double.Parse(Lua.GetReturnValues(lua)[0]);
            }
            catch
            {
                Log("Lua failed in SpellCooldown: " + lua);
                return 99999;
            }
        }

        public bool IsUsableSpell(string name)
        {
            name = LocalizeSpellName(name);
            var lua = string.Format("local x=select(1, IsUsableSpell(\"{0}\")); if x==nil then return 0 else return 1 end", RealLuaEscape(name));
            try
            {
                return Lua.GetReturnValues(lua)[0] == "1";
            }
            catch
            {
                Log("Lua failed in IsUsableSpell: " + lua);
                return false;
            }
        }

        public double PlayerBuffTimeLeft(string name)
        {
            name = LocalizeSpellName(name);
            try
            {
                var lua = string.Format("local x=select(7, UnitBuff('player', \"{0}\", nil, 'PLAYER')); if x==nil then return 0 else return x-GetTime() end", RealLuaEscape(name));
                var t = double.Parse(Lua.GetReturnValues(lua)[0]);
                return t;
            }
            catch
            {
                Log("Lua failed in PlayerBuffTimeLeft");
                return 999999;
            }
        }

        public double GCD { get { return SpellManager.GlobalCooldownLeft.TotalSeconds; } }

        public int PlayerCountBuff(string name)
        {
            name = LocalizeSpellName(name);
            try
            {
                var lua = string.Format("local x=select(4, UnitBuff('player', \"{0}\")); if x==nil then return 0 else return x end", RealLuaEscape(name));
                var t = int.Parse(Lua.GetReturnValues(lua)[0]);
                return t;
            }
            catch
            {
                Log("Lua failed in PlayerCountBuff");
                return 0;
            }
        }

        public int TargetCountDebuff(string name)
        {
            name = LocalizeSpellName(name);
            try
            {
                var lua = string.Format("local x=select(4, UnitDebuff('target', \"{0}\", nil, 'PLAYER')); if x==nil then return 0 else return x end", RealLuaEscape(name));
                var t = int.Parse(Lua.GetReturnValues(lua)[0]);
                return t;
            }
            catch
            {
                Log("Lua failed in TargetCountDebuff");
                return 0;
            }
        }

        public int TargetCountBuff(string name)
        {
            name = LocalizeSpellName(name);
            try
            {
                var lua = string.Format("local x=select(4, UnitBuff('target', \"{0}\", nil, 'PLAYER')); if x==nil then return 0 else return x end", RealLuaEscape(name));
                var t = int.Parse(Lua.GetReturnValues(lua)[0]);
                return t;
            }
            catch
            {
                Log("Lua failed in TargetCountBuff");
                return 0;
            }
        }

        public int CountEnnemiesInRange(WoWPoint fromLocation, double maxRange)
        {
            return GroupBase.Instance.NearbyUnits(fromLocation, maxRange, GroupBase.Instance.Logic == GroupBase.GroupLogic.BATTLEGROUND).Count;
        }

        public double TargetDebuffTimeLeft(string name)
        {
            name = LocalizeSpellName(name);
            try
            {
                var lua = string.Format("local x=select(7, UnitDebuff(\"target\", \"{0}\", nil, 'PLAYER')); if x==nil then return 0 else return x-GetTime() end", RealLuaEscape(name));
                var t = double.Parse(Lua.GetReturnValues(lua)[0]);
                return t;
            }
            catch
            {
                Log("Lua failed in TargetDebuffTimeLeft");
                return 999999;
            }
        }

        public Composite CastBuff(string name, CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(
                delegate(object a)
                {
                    return !PlayerHasBuff(name) && cond(a) && SpellManager.CanBuff(name, Me);
                },
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => CastSpell(name))
                )
            );
        }

        public Composite CastRaidBuff(string name, CanRunDecoratorDelegate cond, string label)
        {
            return CastBuff(name,
                delegate (object a) {
                    if (!cond(a))
                        return false;
                    
                    var players = new List<WoWPlayer>();
                    players.Add(Me);
                    if (Me.IsInRaid)
                        players.AddRange(Me.RaidMembers);
                    else if (Me.IsInParty)
                        players.AddRange(Me.PartyMembers);

                    return players.Any(x => x.Distance2DSqr < 40*40 && !x.Buffs.ContainsKey(name));
                },
                label
            );
        }

        public Composite StopCast(CanRunDecoratorDelegate cond, string label)
        {
            return new Decorator(x => (Me.IsCasting || Me.ChanneledCastingSpellId > 0) && cond(x),
                new Sequence(
                    new TreeSharp.Action(a => Log(label)),
                    new TreeSharp.Action(a => SpellManager.StopCasting())
                )
            );
        }
    }
}
